use std::{ffi::OsString, io};

use kunai_common::{
    bpf_events::{self, TaskInfo, Type, COMM_SIZE},
    uuid::ProcUuid,
};
use thiserror::Error;

use crate::util::{get_clk_tck, ktime_get_ns};

#[derive(Debug, Error)]
pub enum Error {
    #[error("procfs lacks mnt namespace")]
    NoMntNamespace,
    #[error("procfs: {0}")]
    Procfs(#[from] procfs::ProcError),
    #[error("io: {0}")]
    Io(#[from] io::Error),
}

/// Structure encoding the necessary information to
/// build an event generated by kunai process.
#[derive(Debug, Clone, Copy)]
pub struct AgentEventInfo {
    task: TaskInfo,
    parent: TaskInfo,
}

impl AgentEventInfo {
    #[inline(always)]
    fn task_info_from_proc(p: procfs::process::Process) -> Result<TaskInfo, Error> {
        let stat = p.stat()?;
        let status = p.status()?;
        let namespaces = p.namespaces()?;
        let clk_tck = get_clk_tck()? as u64;

        let mnt = namespaces
            .0
            .get(&OsString::from("mnt"))
            .ok_or(Error::NoMntNamespace)?;

        let comm_bytes = stat.comm.as_bytes();
        let mut comm = [0; COMM_SIZE];
        comm.iter_mut().enumerate().for_each(|(i, b)| {
            if let Some(v) = comm_bytes.get(i) {
                *b = *v
            }
        });

        // converts time to nanoseconds
        let start_time = (stat.starttime / clk_tck) * 1_000_000_000;

        Ok(TaskInfo {
            flags: stat.flags,
            pid: stat.pid,
            tgid: status.tgid,
            uid: status.euid,
            gid: status.egid,
            tg_uuid: ProcUuid::new(start_time, 0, status.tgid as u32),
            namespaces: Some(bpf_events::Namespaces {
                mnt: mnt.identifier as u32,
            }),
            comm,
            zombie: false,
            start_time,
        })
    }

    pub fn from_procfs() -> Result<Self, Error> {
        let task = procfs::process::Process::myself()?;
        let parent = procfs::process::Process::new(task.status()?.ppid)?;

        Ok(Self {
            task: Self::task_info_from_proc(task)?,
            parent: Self::task_info_from_proc(parent)?,
        })
    }

    fn new_event_info(&self, ty: Type) -> Result<bpf_events::EventInfo, Error> {
        Ok(bpf_events::EventInfo {
            etype: ty,
            process: self.task,
            parent: self.parent,
            uuid: kunai_common::uuid::Uuid::new_v4(),
            batch: 0,
            timestamp: ktime_get_ns()?,
        })
    }

    pub fn new_event_with_data<D>(&self, ty: Type, data: D) -> Result<bpf_events::Event<D>, Error> {
        Ok(bpf_events::Event {
            info: self.new_event_info(ty)?,
            data,
        })
    }
}
